﻿/*
 * Copyright (c) 2020-2025, Qingdao Digital Intelligent Ship & Ocean Technology Co., Ltd.
 * All rights reserved.
 *
 * This file is part of FastCAE and is distributed under the terms of the
 * BSD 3-Clause License. See the LICENSE file in the project root for details.
 */

/**********************************************************************
 * @file   FITKGraphLegendScaleActor.cpp
 * @brief  External vtkLegendScaleActor with FITKGraphAxisActor2D.
 * @author ZhouZhaoYi (zhouzhaoyi@diso.cn)
 * @date   2025-07-16
 *********************************************************************/

#include "FITKGraphLegendScaleActor.h"
#include "FITKGraphAxisActor2D.h"

#include <vtkActor2D.h>
#include <vtkCamera.h>
#include <vtkCellArray.h>
#include <vtkCellData.h>
#include <vtkCommand.h>
#include <vtkCoordinate.h>
#include <vtkMath.h>
#include <vtkObjectFactory.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkRenderer.h>
#include <vtkTextMapper.h>
#include <vtkTextProperty.h>
#include <vtkUnsignedCharArray.h>
#include <vtkWindow.h>

 //vtkStandardNewMacro(FITKGraphLegendScaleActor);

 //------------------------------------------------------------------------------
FITKGraphLegendScaleActor* FITKGraphLegendScaleActor::New()
{
    return new FITKGraphLegendScaleActor;
}

//------------------------------------------------------------------------------
FITKGraphLegendScaleActor::FITKGraphLegendScaleActor()
{
    this->LabelMode = DISTANCE;

    this->RightBorderOffset = 50;
    this->TopBorderOffset = 30;
    this->LeftBorderOffset = 50;
    this->BottomBorderOffset = 30;
    this->CornerOffsetFactor = 2.0;

    this->RightAxis = FITKGraphAxisActor2D::New();
    this->RightAxis->GetPositionCoordinate()->SetCoordinateSystemToViewport();
    this->RightAxis->GetPosition2Coordinate()->SetCoordinateSystemToViewport();
    this->RightAxis->GetPositionCoordinate()->SetReferenceCoordinate(nullptr);
    this->RightAxis->SetFontFactor(0.6);
    this->RightAxis->SetNumberOfLabels(5);
    this->RightAxis->AdjustLabelsOff();

    this->TopAxis = FITKGraphAxisActor2D::New();
    this->TopAxis->GetPositionCoordinate()->SetCoordinateSystemToViewport();
    this->TopAxis->GetPosition2Coordinate()->SetCoordinateSystemToViewport();
    this->TopAxis->GetPositionCoordinate()->SetReferenceCoordinate(nullptr);
    this->TopAxis->SetFontFactor(0.6);
    this->TopAxis->SetNumberOfLabels(5);
    this->TopAxis->AdjustLabelsOff();

    this->LeftAxis = FITKGraphAxisActor2D::New();
    this->LeftAxis->GetPositionCoordinate()->SetCoordinateSystemToViewport();
    this->LeftAxis->GetPosition2Coordinate()->SetCoordinateSystemToViewport();
    this->LeftAxis->GetPositionCoordinate()->SetReferenceCoordinate(nullptr);
    this->LeftAxis->SetFontFactor(0.6);
    this->LeftAxis->SetNumberOfLabels(5);
    this->LeftAxis->AdjustLabelsOff();

    this->BottomAxis = FITKGraphAxisActor2D::New();
    this->BottomAxis->GetPositionCoordinate()->SetCoordinateSystemToViewport();
    this->BottomAxis->GetPosition2Coordinate()->SetCoordinateSystemToViewport();
    this->BottomAxis->GetPositionCoordinate()->SetReferenceCoordinate(nullptr);
    this->BottomAxis->SetFontFactor(0.6);
    this->BottomAxis->SetNumberOfLabels(5);
    this->BottomAxis->AdjustLabelsOff();

    this->RightAxisVisibility = 1;
    this->TopAxisVisibility = 1;
    this->LeftAxisVisibility = 1;
    this->BottomAxisVisibility = 1;

    this->LegendVisibility = 1;
    this->Legend = vtkPolyData::New();
    this->LegendPoints = vtkPoints::New();
    this->Legend->SetPoints(this->LegendPoints);
    this->LegendMapper = vtkPolyDataMapper2D::New();
    this->LegendMapper->SetInputData(this->Legend);
    this->LegendActor = vtkActor2D::New();
    this->LegendActor->SetMapper(this->LegendMapper);

    // Create the legend
    vtkIdType pts[4];
    this->LegendPoints->SetNumberOfPoints(10);
    vtkCellArray* legendPolys = vtkCellArray::New();
    legendPolys->AllocateEstimate(4, 4);
    pts[0] = 0;
    pts[1] = 1;
    pts[2] = 6;
    pts[3] = 5;
    legendPolys->InsertNextCell(4, pts);
    pts[0] = 1;
    pts[1] = 2;
    pts[2] = 7;
    pts[3] = 6;
    legendPolys->InsertNextCell(4, pts);
    pts[0] = 2;
    pts[1] = 3;
    pts[2] = 8;
    pts[3] = 7;
    legendPolys->InsertNextCell(4, pts);
    pts[0] = 3;
    pts[1] = 4;
    pts[2] = 9;
    pts[3] = 8;
    legendPolys->InsertNextCell(4, pts);
    this->Legend->SetPolys(legendPolys);
    legendPolys->Delete();

    // Create the cell data
    vtkUnsignedCharArray* colors = vtkUnsignedCharArray::New();
    colors->SetNumberOfComponents(3);
    colors->SetNumberOfTuples(4);
    colors->SetTuple3(0, 0, 0, 0);
    colors->SetTuple3(1, 255, 255, 255);
    colors->SetTuple3(2, 0, 0, 0);
    colors->SetTuple3(3, 255, 255, 255);
    this->Legend->GetCellData()->SetScalars(colors);
    colors->Delete();

    // Now the text. The first five are for the 0,1/4,1/2,3/4,1 labels.
    this->LegendTitleProperty = vtkTextProperty::New();
    this->LegendTitleProperty->SetJustificationToCentered();
    this->LegendTitleProperty->SetVerticalJustificationToBottom();
    this->LegendTitleProperty->SetBold(1);
    this->LegendTitleProperty->SetItalic(1);
    this->LegendTitleProperty->SetShadow(1);
    this->LegendTitleProperty->SetFontFamilyToArial();
    this->LegendTitleProperty->SetFontSize(10);
    this->LegendLabelProperty = vtkTextProperty::New();
    this->LegendLabelProperty->SetJustificationToCentered();
    this->LegendLabelProperty->SetVerticalJustificationToTop();
    this->LegendLabelProperty->SetBold(1);
    this->LegendLabelProperty->SetItalic(1);
    this->LegendLabelProperty->SetShadow(1);
    this->LegendLabelProperty->SetFontFamilyToArial();
    this->LegendLabelProperty->SetFontSize(8);
    for (int i = 0; i < 6; i++)
    {
        this->LabelMappers[i] = vtkTextMapper::New();
        this->LabelMappers[i]->SetTextProperty(this->LegendLabelProperty);
        this->LabelActors[i] = vtkActor2D::New();
        this->LabelActors[i]->SetMapper(this->LabelMappers[i]);
    }
    this->LabelMappers[5]->SetTextProperty(this->LegendTitleProperty);
    this->LabelMappers[0]->SetInput("0");
    this->LabelMappers[1]->SetInput("1/4");
    this->LabelMappers[2]->SetInput("1/2");
    this->LabelMappers[3]->SetInput("3/4");
    this->LabelMappers[4]->SetInput("1");

    this->Coordinate = vtkCoordinate::New();
    this->Coordinate->SetCoordinateSystemToDisplay();
}

//------------------------------------------------------------------------------
FITKGraphLegendScaleActor::~FITKGraphLegendScaleActor()
{
    this->RightAxis->Delete();
    this->TopAxis->Delete();
    this->LeftAxis->Delete();
    this->BottomAxis->Delete();

    this->Legend->Delete();
    this->LegendPoints->Delete();
    this->LegendMapper->Delete();
    this->LegendActor->Delete();

    for (int i = 0; i < 6; i++)
    {
        this->LabelMappers[i]->Delete();
        this->LabelActors[i]->Delete();
    }
    this->LegendTitleProperty->Delete();
    this->LegendLabelProperty->Delete();
    this->Coordinate->Delete();
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::GetActors2D(vtkPropCollection* pc)
{
    pc->AddItem(this->RightAxis);
    pc->AddItem(this->TopAxis);
    pc->AddItem(this->LeftAxis);
    pc->AddItem(this->BottomAxis);
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::ReleaseGraphicsResources(vtkWindow* w)
{
    this->RightAxis->ReleaseGraphicsResources(w);
    this->TopAxis->ReleaseGraphicsResources(w);
    this->LeftAxis->ReleaseGraphicsResources(w);
    this->BottomAxis->ReleaseGraphicsResources(w);

    this->LegendActor->ReleaseGraphicsResources(w);

    for (int i = 0; i < 6; i++)
    {
        this->LabelActors[i]->ReleaseGraphicsResources(w);
    }
}

//------------------------------------------------------------------------------
int FITKGraphLegendScaleActor::RenderOpaqueGeometry(vtkViewport* viewport)
{
    this->BuildRepresentation(viewport);

    int renderedSomething = 0;
    if (this->RightAxisVisibility)
    {
        renderedSomething = this->RightAxis->RenderOpaqueGeometry(viewport);
    }
    if (this->TopAxisVisibility)
    {
        renderedSomething += this->TopAxis->RenderOpaqueGeometry(viewport);
    }
    if (this->LeftAxisVisibility)
    {
        renderedSomething += this->LeftAxis->RenderOpaqueGeometry(viewport);
    }
    if (this->BottomAxisVisibility)
    {
        renderedSomething += this->BottomAxis->RenderOpaqueGeometry(viewport);
    }
    if (this->LegendVisibility)
    {
        renderedSomething += this->LegendActor->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[0]->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[1]->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[2]->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[3]->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[4]->RenderOpaqueGeometry(viewport);
        renderedSomething += this->LabelActors[5]->RenderOpaqueGeometry(viewport);
    }

    return renderedSomething;
}

//------------------------------------------------------------------------------
int FITKGraphLegendScaleActor::RenderOverlay(vtkViewport* viewport)
{
    int renderedSomething = 0;
    if (this->RightAxisVisibility)
    {
        renderedSomething = this->RightAxis->RenderOverlay(viewport);
    }
    if (this->TopAxisVisibility)
    {
        renderedSomething += this->TopAxis->RenderOverlay(viewport);
    }
    if (this->LeftAxisVisibility)
    {
        renderedSomething += this->LeftAxis->RenderOverlay(viewport);
    }
    if (this->BottomAxisVisibility)
    {
        renderedSomething += this->BottomAxis->RenderOverlay(viewport);
    }
    if (this->LegendVisibility)
    {
        renderedSomething += this->LegendActor->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[0]->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[1]->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[2]->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[3]->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[4]->RenderOverlay(viewport);
        renderedSomething += this->LabelActors[5]->RenderOverlay(viewport);
    }

    return renderedSomething;
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::BuildRepresentation(vtkViewport* viewport)
{
    if (true) // it's probably best just to rerender every time
        //   if ( this->GetMTime() > this->BuildTime ||
        //        (this->Renderer && this->Renderer->GetVTKWindow() &&
        //         this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime) )
    {
        // Specify the locations of the axes.
        const int* size = viewport->GetSize();

        this->RightAxis->GetPositionCoordinate()->SetValue(
            size[0] - this->RightBorderOffset, this->CornerOffsetFactor * this->BottomBorderOffset, 0.0);
        this->RightAxis->GetPosition2Coordinate()->SetValue(size[0] - this->RightBorderOffset,
            size[1] - this->CornerOffsetFactor * this->TopBorderOffset, 0.0);

        this->TopAxis->GetPositionCoordinate()->SetValue(
            size[0] - this->CornerOffsetFactor * this->RightBorderOffset, size[1] - this->TopBorderOffset,
            0.0);
        this->TopAxis->GetPosition2Coordinate()->SetValue(
            this->CornerOffsetFactor * this->LeftBorderOffset, size[1] - this->TopBorderOffset, 0.0);

        this->LeftAxis->GetPositionCoordinate()->SetValue(
            this->LeftBorderOffset, size[1] - this->CornerOffsetFactor * this->TopBorderOffset, 0.0);
        this->LeftAxis->GetPosition2Coordinate()->SetValue(
            this->LeftBorderOffset, this->CornerOffsetFactor * this->BottomBorderOffset, 0.0);

        if (this->LegendVisibility)
        {
            this->BottomAxis->GetPositionCoordinate()->SetValue(
                this->CornerOffsetFactor * this->LeftBorderOffset, 2 * this->BottomBorderOffset, 0.0);
            this->BottomAxis->GetPosition2Coordinate()->SetValue(
                size[0] - this->CornerOffsetFactor * this->RightBorderOffset, 2 * this->BottomBorderOffset,
                0.0);
        }
        else
        {
            this->BottomAxis->GetPositionCoordinate()->SetValue(
                this->CornerOffsetFactor * this->LeftBorderOffset, this->BottomBorderOffset, 0.0);
            this->BottomAxis->GetPosition2Coordinate()->SetValue(
                size[0] - this->CornerOffsetFactor * this->RightBorderOffset, this->BottomBorderOffset,
                0.0);
        }

        // Now specify the axis values
        if (this->LabelMode == XY_COORDINATES)
        {
            double* xL = this->RightAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            double* xR = this->RightAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            this->RightAxis->SetRange(xL[1], xR[1]);

            xL = this->TopAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->TopAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            this->TopAxis->SetRange(xL[0], xR[0]);

            xL = this->LeftAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->LeftAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            this->LeftAxis->SetRange(xL[1], xR[1]);

            xL = this->BottomAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->BottomAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            this->BottomAxis->SetRange(xL[0], xR[0]);
        }
        else // distance between points
        {
            double d;

            double* xL = this->RightAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            double* xR = this->RightAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            d = sqrt(vtkMath::Distance2BetweenPoints(xL, xR));
            this->RightAxis->SetRange(-d / 2.0, d / 2.0);

            xL = this->TopAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->TopAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            d = sqrt(vtkMath::Distance2BetweenPoints(xL, xR));
            this->TopAxis->SetRange(d / 2.0, -d / 2.0);

            xL = this->LeftAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->LeftAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            d = sqrt(vtkMath::Distance2BetweenPoints(xL, xR));
            this->LeftAxis->SetRange(d / 2.0, -d / 2.0);

            xL = this->BottomAxis->GetPositionCoordinate()->GetComputedWorldValue(viewport);
            xR = this->BottomAxis->GetPosition2Coordinate()->GetComputedWorldValue(viewport);
            d = sqrt(vtkMath::Distance2BetweenPoints(xL, xR));
            this->BottomAxis->SetRange(-d / 2.0, d / 2.0);
        }

        if (this->LegendVisibility)
        {
            // Update the position
            double x1 = 0.33333 * size[0];
            double delX = x1 / 4.0;

            this->LegendPoints->SetPoint(0, x1, 10, 0);
            this->LegendPoints->SetPoint(1, x1 + delX, 10, 0);
            this->LegendPoints->SetPoint(2, x1 + 2 * delX, 10, 0);
            this->LegendPoints->SetPoint(3, x1 + 3 * delX, 10, 0);
            this->LegendPoints->SetPoint(4, x1 + 4 * delX, 10, 0);
            this->LegendPoints->SetPoint(5, x1, 20, 0);
            this->LegendPoints->SetPoint(6, x1 + delX, 20, 0);
            this->LegendPoints->SetPoint(7, x1 + 2 * delX, 20, 0);
            this->LegendPoints->SetPoint(8, x1 + 3 * delX, 20, 0);
            this->LegendPoints->SetPoint(9, x1 + 4 * delX, 20, 0);
            this->LegendPoints->Modified();

            // Specify the position of the legend title
            this->LabelActors[5]->SetPosition(0.5 * size[0], 22);
            this->Coordinate->SetValue(0.33333 * size[0], 15, 0.0);
            double* x = this->Coordinate->GetComputedWorldValue(viewport);
            double xL[3];
            xL[0] = x[0];
            xL[1] = x[1];
            xL[2] = x[2];
            this->Coordinate->SetValue(0.66667 * size[0], 15, 0.0);
            x = this->Coordinate->GetComputedWorldValue(viewport);
            double xR[3];
            xR[0] = x[0];
            xR[1] = x[1];
            xR[2] = x[2];
            double len = sqrt(vtkMath::Distance2BetweenPoints(xL, xR));
            char buf[256];
            snprintf(buf, sizeof(buf), "Scale 1 : %g", len);
            this->LabelMappers[5]->SetInput(buf);

            // Now specify the position of the legend labels
            double* x_pt = this->LegendPoints->GetPoint(0);
            this->LabelActors[0]->SetPosition(x_pt[0], x_pt[1] - 1);
            x_pt = this->LegendPoints->GetPoint(1);
            this->LabelActors[1]->SetPosition(x_pt[0], x_pt[1] - 1);
            x_pt = this->LegendPoints->GetPoint(2);
            this->LabelActors[2]->SetPosition(x_pt[0], x_pt[1] - 1);
            x_pt = this->LegendPoints->GetPoint(3);
            this->LabelActors[3]->SetPosition(x_pt[0], x_pt[1] - 1);
            x_pt = this->LegendPoints->GetPoint(4);
            this->LabelActors[4]->SetPosition(x_pt[0], x_pt[1] - 1);
        }

        this->BuildTime.Modified();
    }
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::AllAnnotationsOn()
{
    if (this->RightAxisVisibility && this->TopAxisVisibility && this->LeftAxisVisibility &&
        this->BottomAxisVisibility && this->LegendVisibility)
    {
        return;
    }

    // If here, we are modified and something gets turned on
    this->RightAxisVisibility = 1;
    this->TopAxisVisibility = 1;
    this->LeftAxisVisibility = 1;
    this->BottomAxisVisibility = 1;
    this->LegendVisibility = 1;
    this->Modified();
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::AllAnnotationsOff()
{
    if (!this->RightAxisVisibility && !this->TopAxisVisibility && !this->LeftAxisVisibility &&
        !this->BottomAxisVisibility && !this->LegendVisibility)
    {
        return;
    }

    // If here, we are modified and something gets turned off
    this->RightAxisVisibility = 0;
    this->TopAxisVisibility = 0;
    this->LeftAxisVisibility = 0;
    this->BottomAxisVisibility = 0;
    this->LegendVisibility = 0;
    this->Modified();
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::AllAxesOn()
{
    if (this->RightAxisVisibility && this->TopAxisVisibility && this->LeftAxisVisibility &&
        this->BottomAxisVisibility)
    {
        return;
    }

    // If here, we are modified and something gets turned on
    this->RightAxisVisibility = 1;
    this->TopAxisVisibility = 1;
    this->LeftAxisVisibility = 1;
    this->BottomAxisVisibility = 1;
    this->Modified();
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::AllAxesOff()
{
    if (!this->RightAxisVisibility && !this->TopAxisVisibility && !this->LeftAxisVisibility &&
        !this->BottomAxisVisibility)
    {
        return;
    }

    // If here, we are modified and something gets turned off
    this->RightAxisVisibility = 0;
    this->TopAxisVisibility = 0;
    this->LeftAxisVisibility = 0;
    this->BottomAxisVisibility = 0;
    this->Modified();
}

//------------------------------------------------------------------------------
void FITKGraphLegendScaleActor::PrintSelf(ostream& os, vtkIndent indent)
{
    // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
    this->Superclass::PrintSelf(os, indent);

    os << indent << "Label Mode: ";
    if (this->LabelMode == DISTANCE)
    {
        os << "Distance\n";
    }
    else // if ( this->LabelMode == DISTANCE )
    {
        os << "XY_Coordinates\n";
    }

    os << indent << "Right Axis Visibility: " << (this->RightAxisVisibility ? "On\n" : "Off\n");
    os << indent << "Top Axis Visibility: " << (this->TopAxisVisibility ? "On\n" : "Off\n");
    os << indent << "Left Axis Visibility: " << (this->LeftAxisVisibility ? "On\n" : "Off\n");
    os << indent << "Bottom Axis Visibility: " << (this->BottomAxisVisibility ? "On\n" : "Off\n");
    os << indent << "Legend Visibility: " << (this->LegendVisibility ? "On\n" : "Off\n");
    os << indent << "Corner Offset Factor: " << this->CornerOffsetFactor << "\n";

    os << indent << "Right Border Offset: " << this->RightBorderOffset << "\n";
    os << indent << "Top Border Offset: " << this->TopBorderOffset << "\n";
    os << indent << "Left Border Offset: " << this->LeftBorderOffset << "\n";
    os << indent << "Bottom Border Offset: " << this->BottomBorderOffset << "\n";

    os << indent << "Legend Title Property: ";
    if (this->LegendTitleProperty)
    {
        os << this->LegendTitleProperty << "\n";
    }
    else
    {
        os << "(none)\n";
    }
    os << indent << "Legend Label Property: ";
    if (this->LegendLabelProperty)
    {
        os << this->LegendLabelProperty << "\n";
    }
    else
    {
        os << "(none)\n";
    }

    os << indent << "Right Axis: ";
    if (this->RightAxis)
    {
        os << this->RightAxis << "\n";
    }
    else
    {
        os << "(none)\n";
    }
    os << indent << "Top Axis: ";
    if (this->TopAxis)
    {
        os << this->TopAxis << "\n";
    }
    else
    {
        os << "(none)\n";
    }
    os << indent << "Left Axis: ";
    if (this->LeftAxis)
    {
        os << this->LeftAxis << "\n";
    }
    else
    {
        os << "(none)\n";
    }
    os << indent << "Bottom Axis: ";
    if (this->BottomAxis)
    {
        os << this->BottomAxis << "\n";
    }
    else
    {
        os << "(none)\n";
    }
}